<template>
  <el-form
    ref="ruleForm"
    :model="form"
    size="small"
  >
    <el-table
      style="width: 100%"
      :data="form.list"
      v-bind="$attrs"
      v-on="$listeners"
    >
      <el-table-column
        v-for="col in columns"
        :key="col.prop"
        :column-key="col.prop"
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
        :fixed="col.fixed"
        :align="col.align"
        :header-align="col.headerAlign"
        :show-overflow-tooltip="
          col.showOverflowTooltip === false || col.prop === COLUMN_OPERATE_PROP
            ? false
            : true
        "
        :filters="col.filters && col.filters"
      >
        <template slot-scope="{ row, $index }">
          <!-- 编辑行 -->
          <template v-if="(row.isEdit || row.isAdd) && col.valueType">
            <el-form-item
              :prop="`list.${$index}.${col.prop}`"
              :rules="col.rules"
              style="margin-bottom: 14px"
            >
              <component
                :is="`el-${col.valueType}`"
                v-model="row[col.prop]"
                v-bind="setCellAttr(col, row, $index).bind"
                v-on="setCellAttr(col, row, $index).on"
              >
                <template v-if="col.valueType === 'select'">
                  <el-option
                    v-for="op in col.options"
                    :key="op.value"
                    :label="op.label"
                    :value="op.value"
                  ></el-option>
                </template>
              </component>
            </el-form-item>
          </template>
          <template v-else>
            <template v-if="col.prop === COLUMN_OPERATE_PROP">
              <div v-if="row.isEdit || row.isAdd" style="margin-bottom: 14px">
                <el-button
                  size="small"
                  type="text"
                  @click="saveData(row, $index)"
                >
                  保存
                </el-button>
                <el-button
                  size="small"
                  type="text"
                  @click="handleCancel(row, $index)"
                >
                  取消
                </el-button>
              </div>
              <template v-else>
                <slot :name="col.prop" :row="row" :index="$index" :col="col">
                  <el-button size="small" type="text"  @click="editData(row)">
                    编辑
                  </el-button>
                  <el-button
                    size="small"
                    type="text"
                    @click="deleteData($index)"
                  >
                    删除
                  </el-button>
                </slot>
              </template>
            </template>
            <template v-else>
              <slot :name="col.prop" :row="row" :index="$index" :col="col">
                {{ renderCellData(col, row, $index) }}
              </slot>
            </template>
          </template>
        </template>
      </el-table-column>
    </el-table>

    <Pagination v-if="pagination" :pagination="pagData" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  </el-form>
</template>

<script>
import Pagination from "./components/Pagination.vue";
// 表格操作列prop
const COLUMN_OPERATE_PROP = "operation"

// 编辑表格
// @group Table
export default {
  name: "EditTable",
  inheritAttrs: false,
  components: {
    Pagination,
  },
  props: {
    columns: {
      type: Array,
      default: () => ([]),
    },
    rowKey: {
      type: String,
      default: "id",
    },
    // 分页器配置
    pagination: {
      type: [Object, Boolean],
      default: true,
    },
    // 额外查询参数
    params: {
      type: Object,
      default: () => ({}),
    },
    // 查询
    request: {
      type: Function,
      default: () => new Promise((resolve) => resolve({})),
    },
    // 是否初始化时加载表格数据
    requestAuto: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    this.COLUMN_OPERATE_PROP = COLUMN_OPERATE_PROP;
    return {
      form: {
        list: [],
      },
      // 缓存编辑前数据
      cacheRows: {},
      // 分页器
      pagData: {
        pageNum: 1,
        pageSize: 10,
        total: 0,
      },
      // 加载状态
      loading: false,
    };
  },
  watch: {
    params: {
      handler() {
        this.handleRequest();
      },
      deep: true
    }
  },
  created() {
    this.init();
  },
  methods: {
    isFunction(fn) {
      return typeof fn === "function";
    },
    init() {
      this.initRequestParam();
      if (this.requestAuto) {
        this.handleRequest();
      }
    },
    // 更新分页器
    setPag(value) {
      this.pagData = { ...this.pagData, ...value };
    },
    // 初始化请求参数
    initRequestParam() {
      this.setPag(this.pagination);
    },
    // 获取请求参数
    getRequestParam() {
      const { pageNum, pageSize } = this.pagData;
      return {
        pageNum,
        pageSize,
        ...this.params
      };
    },
    // 查询表格
    async handleRequest() {
      if (!this.isFunction(this.request)) return;
      this.loading = true;
      const reqParams = this.getRequestParam()
      const res = await this.request(reqParams);
      if (res) {
        const { list, total } = res;
        this.setData(list);
        this.setPag({ total });
      }
      this.loading = false;
    },
    renderCellData(item, row, index) {
      if (typeof item.customRender === "function") {
        return item.customRender(row, index)
      }
      const cellValue = row[item.prop];
      const list = item.options
      if (list) {
        return list.find(item => item.value === cellValue)?.label
      }
      return cellValue ?? "-";

    },
    // 表格页数页码、筛选条件改变
    onChange(pag) {
      // 判断是否正在编辑状态或添加状态
      if (this.isAddOrEdit()) {
        this.$message.warning("请先保存或取消正在编辑行");
        return;
      }
      this.setPag(pag);
      this.handleRequest();
    },
    handleCurrentChange(pageNum) {
      this.onChange({ pageNum });
    },
    handleSizeChange(pageSize) {
      const pag = {
        pageNum: 1,
        pageSize,
      };
      this.onChange(pag);
    },
    // 设置绑定单元格属性、事件
    setCellAttr(col, row, index) {
      const obj = {
        bind: col.bind || {},
        on: col.on || {},
      };
      const { customCell } = col;
      if (this.isFunction(customCell)) {
        const { on = {}, ...attr } = customCell(row, index);
        obj.bind = {
          ...obj.bind,
          ...attr,
        };
        obj.on = {
          ...obj.on,
          ...on,
        };
      }
      return obj;
    },
    // 是否处于添加或者编辑状态
    isAddOrEdit() {
      return this.form.list.some((v) => v.isEdit || v.isAdd)
    },
    // @vuese
    // 回显数据
    // @arg Array
    setData(formList) {
      if (!formList || !formList.length) {
        this.form.list = [];
        return;
      }
      const list = formList.map(rowItem => ({
        ...rowItem,
        isEdit: false,
        isAdd: false
      }))
      Object.assign(this.form, { list });
      // 重置缓存表格行
      this.cacheRows = {};
      this.$nextTick(() => {
        this.$refs.ruleForm && this.$refs.ruleForm.clearValidate();
      });
    },
    // @vuese
    // 获取数据
    getData() {
      return this.form.list.map(item => {
        const { isEdit, isAdd, ...rest } = item;
        return rest;
      });
    },
    // @vuese
    // 添加数据
    // @arg row
    addData(row = {}) {
      // 判断是否正在编辑状态或添加状态
      if (this.isAddOrEdit()) {
        this.$message.warning("请先保存或取消正在编辑行");
        return;
      }
      const formItem = this.columns
        .filter((v) => v.prop !== COLUMN_OPERATE_PROP)
        .reduce(
          (r, c) => ({
            ...r,
            [c.prop]: c.valueType === "switch" ? false : "",
          }),
          {
            isAdd: true,
            isEdit: false,
          }
        );
      const obj = {
        ...formItem,
        ...row,
      };
      obj[this.rowKey] = row[this.rowKey] || Date.now();
      this.form.list.push(obj);
      const key = obj[this.rowKey];
      this.cacheRows[key] = JSON.parse(JSON.stringify(obj));
    },
    // @vuese
    // 点击编辑,进入编辑状态
    // @arg row
    editData(row) {
      row.isEdit = true;
      const key = row[this.rowKey];
      this.cacheRows[key] = JSON.parse(JSON.stringify(row));
    },
    // @vuese
    // 删除数据
    // @arg index
    deleteData(idx) {
      // 删除缓存数据
      const key = this.form.list[idx][this.rowKey]
      delete this.cacheRows[key]
      // 删除表格数据
      this.form.list.splice(idx, 1)
      this.$refs.ruleForm.fields.forEach(n => {
        if (n.prop.split('.')[1] === idx) {
          n.clearValidate()
        }
      })
    },
    // 保存数据
    saveData(row, idx) {
      const ret = Object.keys(row)
        .map((r) => `list.${idx}.${r}`)
        .filter((r) => !/^(isEdit|isAdd)/g.test(r));
      let errCount = 0;
      this.$refs.ruleForm.validateField(ret, (err) => {
        if (err) {
          errCount++;
        }
      });
      // 校验通过
      if (errCount === 0) {
        const { isEdit, isAdd, ...rest } = row
        row.isEdit = false;
        row.isAdd = false;
        this.$set(this.form.list, idx, row);
        // if (isEdit) {
        //   this.$emit("update", rest, this.form.list);
        // }
        // if (isAdd) {
        //   this.$emit("create", rest, this.form.list);
        // }
      }
    },
    // 点击取消
    handleCancel(row, index) {
      if (row.isEdit) {
        const key = row[this.rowKey];
        // 取消编辑,还原数据
        const value = JSON.parse(JSON.stringify(this.cacheRows[key]));
        value.isEdit = false;
        value.isAdd = false;
        this.$set(this.form.list, index, value);
      } else if (row.isAdd) {
        this.$confirm('此操作将不会保存编辑数据, 是否继续?', '提示', { type: 'warning' }).then(() => {
          this.deleteData(index)
        }).catch(() => {
        });
      }
    },
    // 校验表单
    validate(...args) {
      return this.$refs.ruleForm.validate(...args)
    },
    // 批量保存,获取处于添加或编辑状态,通过校验的数据
    getValidData() {
      let newRows = [];
      this.$refs.ruleForm.validate((valid) => {
        // 校验通过
        if (valid) {
          // 筛选添加或编辑状态的表格行
          newRows = this.form.list.filter((v) => v.isAdd || v.isEdit)
        }
      });
      return newRows;
    },
  },
};
</script>

<style scoped>
</style>

